package com.hrbxlh.virtualization.service.impl;

import cn.hutool.core.util.NetUtil;
import com.hrbxlh.virtualization.constant.NovncConstant;
import com.hrbxlh.virtualization.converter.*;
import com.hrbxlh.virtualization.dao.ClusterMapper;
import com.hrbxlh.virtualization.dao.ClusterMapperExt;
import com.hrbxlh.virtualization.dao.ContainerMapper;
import com.hrbxlh.virtualization.dto.ClusterDTO;
import com.hrbxlh.virtualization.enums.ContainerRoleEnum;
import com.hrbxlh.virtualization.exception.GlobalException;
import com.hrbxlh.virtualization.param.ClusterAutoCreateParam;
import com.hrbxlh.virtualization.param.ClusterCreateParam;
import com.hrbxlh.virtualization.param.ClusterUpdateParam;
import com.hrbxlh.virtualization.pojo.Cluster;
import com.hrbxlh.virtualization.pojo.ClusterExample;
import com.hrbxlh.virtualization.pojo.Container;
import com.hrbxlh.virtualization.pojo.Image;
import com.hrbxlh.virtualization.result.ResultEnum;
import com.hrbxlh.virtualization.service.*;
import com.hrbxlh.virtualization.util.SSHUtil;
import com.hrbxlh.virtualization.vo.ClusterVO;
import com.hrbxlh.virtualization.vo.DesktopVO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.naming.ldap.PagedResultsControl;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * @author cheer
 */
@Service
public class ClusterServiceImpl implements ClusterService {

    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private ContainerService containerService;
    @Autowired
    private ClusterMapper clusterMapper;
    @Autowired
    private ClusterMapperExt clusterMapperExt;
    @Autowired
    private ImageService imageService;
    @Autowired
    private ImageAllocationService imageAllocationService;
    @Autowired
    private ContainerMapper containerMapper;
    @Autowired
    private DockerService dockerService;
    @Autowired
    private ClusterCreateParamToClusterConverter clusterCreateParamToClusterConverter;
    @Autowired
    private ClusterAutoCreateParamToClusterConverter clusterAutoCreateParamToClusterConverter;
    @Autowired
    private ClusterDTOToClusterVOConverter clusterDTOToClusterVOConverter;
    @Autowired
    private ClusterToClusterVOConverter clusterToClusterVOConverter;
    @Autowired
    private ClusterUpdateParamToClusterConverter clusterUpdateParamToClusterConverter;

    @Override
    public void createCluster(ClusterCreateParam clusterCreateParam) {
        checkClusterNameExists(clusterCreateParam.getName());
        checkImageExists(clusterCreateParam.getMasterImageId(), clusterCreateParam.getFirstSlaveImageId(),
                clusterCreateParam.getSecondSlaveImageId());

        Cluster cluster = clusterCreateParamToClusterConverter.convert(clusterCreateParam);
        cluster.setShared(true);
        clusterMapper.insertSelective(cluster);
    }

    @Override
    public Long autoCreateCluster(ClusterAutoCreateParam clusterAutoCreateParam) {
        checkImageExists(clusterAutoCreateParam.getMasterImageId(), clusterAutoCreateParam.getFirstSlaveImageId(),
                clusterAutoCreateParam.getSecondSlaveImageId());

        Cluster cluster = clusterAutoCreateParamToClusterConverter.convert(clusterAutoCreateParam);
        cluster.setClusterName(String.format("集群%s(自动生成)", UUID.randomUUID().toString().substring(0, 6)));
        cluster.setShared(false);
        clusterMapper.insertSelective(cluster);

        return cluster.getId();
    }

    @Override
    public List<ClusterVO> listClusters() {
        return listClusters(null);
    }

    @Override
    public List<ClusterVO> listClusters(String stuorteaId) {
        List<ClusterDTO> clusterDTOList = clusterMapperExt.listClusterDTOs(stuorteaId);

        List<ClusterVO> clusterVOList = new ArrayList<>();
        for (ClusterDTO clusterDTO : clusterDTOList) {
            ClusterVO clusterVO = clusterDTOToClusterVOConverter.convert(clusterDTO);
            clusterVOList.add(clusterVO);
        }
        return clusterVOList;
    }

    @Override
    public ClusterVO queryCluster(Long clusterId) {
        Cluster cluster = clusterMapper.selectByPrimaryKey(clusterId);
        if (cluster == null) {
            throw new GlobalException(ResultEnum.CLUSTER_NOT_EXISTS);
        }

        return clusterToClusterVOConverter.convert(cluster);
    }

    @Override
    public void updateCluster(ClusterUpdateParam clusterUpdateParam) {
        checkClusterNameExists(clusterUpdateParam.getId(), clusterUpdateParam.getName());
        checkImageExists(clusterUpdateParam.getMasterImageId(), clusterUpdateParam.getFirstSlaveImageId(),
                clusterUpdateParam.getSecondSlaveImageId());

        Cluster cluster = clusterUpdateParamToClusterConverter.convert(clusterUpdateParam);
        clusterMapper.updateByPrimaryKeySelective(cluster);
    }

    @Override
    @Transactional
    public void deleteCluster(Long clusterId, Boolean isForced) {
        Cluster cluster = clusterMapper.selectByPrimaryKey(clusterId);
        if (cluster == null) {
            throw new GlobalException(ResultEnum.CLUSTER_NOT_EXISTS);
        }
        if (cluster.getInitial()) {
            throw new GlobalException(ResultEnum.CLUSTER_DELETE_INITIAL_NOT_ALLOWED);
        }
        if (imageAllocationService.isClusterAllocated(clusterId) && !isForced) {
            throw new GlobalException(ResultEnum.CLUSTER_IS_USED);
        }

        imageAllocationService.deleteImageAllocationByClusterId(clusterId);
        clusterMapper.deleteByPrimaryKey(clusterId);
    }

    @Override
    @Transactional
    public void deleteCluster(Long clusterId, Boolean isForced, String stuorteaId) {
        Cluster cluster = clusterMapper.selectByPrimaryKey(clusterId);
        if (cluster == null) {
            throw new GlobalException(ResultEnum.CLUSTER_NOT_EXISTS);
        }
        if (!Objects.equals(cluster.getStuorteaId(), stuorteaId)) {
            throw new GlobalException(ResultEnum.CLUSTER_DELETE_NOT_ALLOWED);
        }

        deleteCluster(clusterId, isForced);
    }

    @Override
    public DesktopVO queryDesktop(String stuorteaId, Long clusterId) {
        Cluster cluster = clusterMapper.selectByPrimaryKey(clusterId);
        if (cluster == null) {
            throw new GlobalException(ResultEnum.CLUSTER_NOT_EXISTS);
        }

        Image masterImage = imageService.queryImageById(cluster.getMasterImageId());
        Image firstSlaveImage = imageService.queryImageById(cluster.getFirstSlaveImageId());
        Image secondSlaveImage = imageService.queryImageById(cluster.getSecondSlaveImageId());
        if (masterImage == null || firstSlaveImage == null || secondSlaveImage == null) {
            throw new GlobalException(ResultEnum.CLUSTER_BROKEN);
        }

        DesktopVO desktopVO = new DesktopVO();
        Container master = fillContainerToDesktopVO(stuorteaId, masterImage, ContainerRoleEnum.MASTER, desktopVO);
        Container slave1 = fillContainerToDesktopVO(stuorteaId, firstSlaveImage, ContainerRoleEnum.FIRST_SLAVE, desktopVO);
        Container slave2 = fillContainerToDesktopVO(stuorteaId, secondSlaveImage, ContainerRoleEnum.SECOND_SLAVE, desktopVO);

        modifyEtcHosts(master, slave1, slave2);
        return desktopVO;
    }

    @Override
    public List<Cluster> listClustersByImageId(Long imageId) {
        ClusterExample clusterExample = new ClusterExample();
        clusterExample.or().andMasterImageIdEqualTo(imageId);
        clusterExample.or().andFirstSlaveImageIdEqualTo(imageId);
        clusterExample.or().andSecondSlaveImageIdEqualTo(imageId);
        return clusterMapper.selectByExample(clusterExample);
    }

    @Override
    public List<ClusterVO> listClustersByAllocate(String stuorteaId) {
        List<ClusterDTO> clusterDTOList = clusterMapperExt.listClusterDTOsByAllocate(stuorteaId);

        List<ClusterVO> clusterVOList = new ArrayList<>();
        for (ClusterDTO clusterDTO : clusterDTOList) {
            ClusterVO clusterVO = new ClusterVO();
            clusterVO.setId(clusterDTO.getId());
            clusterVO.setName(clusterDTO.getClusterName());
            clusterVOList.add(clusterVO);
        }
        return clusterVOList;
    }

    @Override
    public boolean isClusterBroken(Long clusterId) {
        Cluster cluster = clusterMapper.selectByPrimaryKey(clusterId);
        return cluster == null || cluster.getBroken();
    }


    @Override
    public void modifyEtcHosts(Container master, Container slave1, Container slave2) {
        String masterIp = NetUtil.longToIpv4(master.getIp());
        String slave1Ip = NetUtil.longToIpv4(slave1.getIp());
        String slave2Ip = NetUtil.longToIpv4(slave2.getIp());

        execCmd(master, generateCmd(masterIp, slave1Ip, slave2Ip));
        execCmd(slave1, generateCmd(masterIp, slave1Ip, slave2Ip));
        execCmd(slave2, generateCmd(masterIp, slave1Ip, slave2Ip));
    }

    @Autowired
    private RestTemplate restTemplate;

    private void execCmd(Container container, String cmd) {
        String host = dockerService.queryHost(container.getRancherId());
        String ip = NetUtil.longToIpv4(container.getIp());
        String url = String.format("http://%s:56000/ssh", host);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("ip", ip);
        map.add("cmd", cmd);
        HttpEntity httpEntity = new HttpEntity<>(map, headers);
        try {
            restTemplate.postForObject(url, httpEntity, Void.class);
        } catch (Exception e) {
            logger.error("发送SSH请求失败：容器RancherId - {}", container.getRancherId(), e);
        }
    }

    private String generateCmd(String masterIp, String slave1Ip, String slave2Ip) {
        return "cat /etc/hosts > /etc/hosts.tmp && " +
                "sed -i '/master/'d /etc/hosts.tmp && " +
                "sed -i '/slave1/'d /etc/hosts.tmp && " +
                "sed -i '/slave2/'d /etc/hosts.tmp && " +
                "sed -i '$a\\" + masterIp + "    master' /etc/hosts.tmp && " +
                "sed -i '$a\\" + slave1Ip + "    slave1' /etc/hosts.tmp && " +
                "sed -i '$a\\" + slave2Ip + "    slave2' /etc/hosts.tmp && " +
                "cat /etc/hosts.tmp > /etc/hosts";
    }


    private void checkClusterNameExists(String clusterName) {
        checkClusterNameExists(null, clusterName);
    }

    private void checkClusterNameExists(Long clusterId, String clusterName) {
        if (clusterMapperExt.selectClusterId(clusterId, clusterName) != null) {
            throw new GlobalException(ResultEnum.CLUSTER_NAME_EXISTS);
        }
    }

    private void checkImageExists(Long masterImageId, Long firstSlaveImageId, Long secondSlaveImageId) {
        if (!imageService.isImageExists(masterImageId)) {
            throw new GlobalException(ResultEnum.CLUSTER_MASTER_IMAGE_NOT_EXISTS);
        }
        if (!imageService.isImageExists(firstSlaveImageId)) {
            throw new GlobalException(ResultEnum.CLUSTER_FIRST_SLAVE_IMAGE_NOT_EXISTS);
        }
        if (!imageService.isImageExists(secondSlaveImageId)) {
            throw new GlobalException(ResultEnum.CLUSTER_SECOND_SLAVE_IMAGE_NOT_EXISTS);
        }
    }

    private Container fillContainerToDesktopVO(String stuorteaId, Image image, ContainerRoleEnum containerRoleEnum,
            DesktopVO desktopVO) {

        String hostname = null;
        if(containerRoleEnum == ContainerRoleEnum.FIRST_SLAVE){
            hostname = "slave1";
        }
        if(containerRoleEnum == ContainerRoleEnum.SECOND_SLAVE){
            hostname = "slave2";
        }
        Container container = containerService.createContainer(image, hostname);
        insertContainer(container, stuorteaId, containerRoleEnum);
        String desktopUrl = dockerService.queryDesktopUrl(container.getRancherId());
        fillContainerToDesktopVO(container, desktopUrl, containerRoleEnum, desktopVO);
        return container;
    }

    private void insertContainer(Container container, String stuorteaId, ContainerRoleEnum containerRoleEnum) {
        container.setStuorteaId(stuorteaId);
        container.setContainerRole(containerRoleEnum.getCode());
        containerMapper.insertSelective(container);
    }

    private void fillContainerToDesktopVO(Container container, String desktopUrl, ContainerRoleEnum containerRoleEnum,
            DesktopVO desktopVO) {

        switch (containerRoleEnum) {
            case MASTER:
                desktopVO.setMasterContainerId(container.getId());
                desktopVO.setMasterImageId(container.getImageId());
                desktopVO.setMasterDesktopUrl(desktopUrl);
                break;
            case FIRST_SLAVE:
                desktopVO.setFirstSlaveContainerId(container.getId());
                desktopVO.setFirstSlaveImageId(container.getImageId());
                desktopVO.setFirstSlaveDesktopUrl(desktopUrl);
                break;
            case SECOND_SLAVE:
                desktopVO.setSecondSlaveContainerId(container.getId());
                desktopVO.setSecondSlaveImageId(container.getImageId());
                desktopVO.setSecondSlaveDesktopUrl(desktopUrl);
                break;
            default:
        }
    }

}
